今までずっと理解しようとすらしてこなかった Vue のスコープ付きスロットを学ぶ
スロット | Vue.js
そもそも Vue のスロットって、
https://scrapbox.io/files/69364b832cd02ea32a942f77.png
例えば子コンポーネント側で button 要素をレンダリングする責務は持ちつつ、その内部のコンテンツについては親コンポーネントの方で提供することができるような仕組みのこと
(props は 英語で書いた方がしっくりくるけどスロットは日本語で書いた方がしっくりくるな)
slots を理解するためには次のような JavaScript の関数と比べるのも大事
code:js
// 親コンポーネントがスロットコンテンツを渡す
FancyButton("Click Me!");
// FancyButton がスロットコンテンツを自身のテンプレート内でレンダリングする
function FancyButton(slotContent) {
return `<button class="fancy-button">
${slotContent}
</button>`
}
フォールバックの使い方もままならないのでこれも押さえておきたい
名前付きスロット
例えば BaseLayout.vue コンポーネント
code:vue
<div class="container">
<header>
<!-- ここに header コンテンツが必要 -->
</header>
<main>
<!-- ここに main コンテンツが必要 -->
</main>
<footer>
<!-- ここに footer コンテンツが必要 -->
</footer>
</div>
code:vue
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
親側では⬇️のように使う
code:vue
<BaseLayout>
<template v-slot:header>
<!-- header スロットのためのコンテンツ -->
</template>
</BaseLayout>
または
code:vue
<BaseLayout>
<template #header>
<!-- header スロットのためのコンテンツ -->
</template>
</BaseLayout>
https://scrapbox.io/files/69364e3315cd5f82749b8212.png
JavaScript の関数と比べると
code:js
// 複数のスロットフラグメントを異なる名前で渡す
BaseLayout({
header: ...,
default: ...,
footer: ...
})
// <BaseLayout> がそれらを異なる場所でレンダリングする
function BaseLayout(slots) {
return `<div class="container">
<header>${slots.header}</header>
<main>${slots.default}</main>
<footer>${slots.footer}</footer>
</div>`
}
ここまではなんとなく理解できている
問題はスコープ付きスロット
前提としてスロットのコンテンツは子コンポーネント内の状態にアクセスできない
これは何かというと子コンポーネント側で定義している ref を親側でスロットから参照できないこと
つまり、親側で定義されているもののみを基本的にはスロットコンテンツとしてして取ることができる
けどスロットのコンテンツが親のスコープと子のスコープのどちらも参照できると便利なこともある
これの実現のためにはレンダリング時に子がデータをスロットに渡すことができる必要がある
props をコンポーネントに渡すのと同様に、属性をスロットレイアウトに渡すことができる
code:vue
<!-- <MyComponent> template -->
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
スロット props を受け取るのは、デフォルトスロットと名前付きスロットを使用するのとは少し異なる
子コンポーネントのタグ上で v-slot を使用する方法
code:vue
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
https://scrapbox.io/files/693651707b3b65c5f752c531.png
分割代入もできる
code:vue
<MyComponent v-slot="{ text, count }">
{{ text }} {{ count }}
</MyComponent>
名前およびスコープ付きスロット​もあるみたい
これが多分一番理解できてない
単純に⬇️の感じ
code:vue
<MyComponent>
<template #header="headerProps">
{{ headerProps }}
</template>
<template #default="defaultProps">
{{ defaultProps }}
</template>
<template #footer="footerProps">
{{ footerProps }}
</template>
</MyComponent>
子側で⬇️のようにして props を渡す
code:vue
<slot name="header" message="hello"></slot>
当然だけど、name 属性はスロットの方の予約語なので props にはもちろん含まれない
名前付きスロットとデフォルトのスコープ付きスロットを混在させる場合は、デフォルトスロットに明示的に <template> タグを使用する必要がある
code:vue
<!-- <MyComponent> template -->
<div>
<slot :message="hello"></slot>
<slot name="footer" />
</div>
code:vue
<!-- この書き方はダメ(コンパイルされない) -->
<MyComponent v-slot="{ message }">
<p>{{ message }}</p>
<template #footer>
<!-- message はデフォルトスロットに属しているからここでは使用できない -->
<p>{{ message }}</p>
</template>
</MyComponent>
code:vue
<MyComponent>
<!-- 明示的なデフォルトスロットを使用する -->
<template #default="{ message }">
<p>{{ message }}</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</MyComponent>
デフォルトスロットに明示的に template タグを使用することで、message props が他のスロット内では使用できないことを明確にできる
(ちょっとややこしいけど確かに良さそうな気がする)
defineSlots マクロ
スコープ付きスロットの型定義のため